package org.budo.activiti.service.impl;

import java.io.InputStream;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.sql.DataSource;

import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricDetail;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.persistence.entity.HistoricDetailVariableInstanceUpdateEntityImpl;
import org.activiti.engine.impl.persistence.entity.HistoricFormPropertyEntityImpl;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.IdentityLinkType;
import org.activiti.engine.task.Task;
import org.budo.activiti.form.type.AllowDenyFormType;
import org.budo.activiti.form.type.DateFormType;
import org.budo.activiti.form.type.TimeFormType;
import org.budo.activiti.service.api.BudoActivitiService;
import org.budo.activiti.util.BpmnModelUtil;
import org.budo.activiti.util.ObjectMapperUtil;
import org.budo.activiti.util.PNGTranscoderUtil;
import org.budo.support.fastjson.Fastjson;
import org.budo.support.javax.sql.util.JdbcUtil;
import org.budo.support.lang.util.ListUtil;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.NumberUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.time.Time;
import org.budo.xml.Xml;
import org.budo.xml.XmlObject;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 * 
 * @see org.budo.activiti.config.BudoActivitiEngineConfiguration
 */
@Slf4j
@Getter
@Setter
@Accessors(chain = true)
public class BudoActivitiServiceImpl implements BudoActivitiService {
    private DataSource dataSource;

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private HistoryService historyService;

    @Resource
    private RuntimeService runtimeService;

    @Resource
    private TaskService taskService;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private FormService formService;

    @Resource
    private TaskEntityManager taskEntityManager;

    /**
     * @see #startProcessInstanceByModelId(String, Map)
     */
    @Override
    public String submitStartFormData(String modelId, Map<String, Object> properties) {
        String processDefinitionId = this.findProcessDefinitionIdByModelId(modelId);
        if (StringUtil.isNullOrEmpty(processDefinitionId)) {
            log.error("#86 submitStartFormData error, processDefinitionId=" + processDefinitionId + ", modelId=" + modelId + ", properties = " + properties);
            return null;
        }

        Map<String, String> _properties = MapUtil.stringMap(properties);
        ProcessInstance processInstance = this.formServiceSubmitStartFormData(processDefinitionId, _properties);

        log.info("#87 startProcessInstanceByModelId, processDefinitionId=" + processDefinitionId //
                + ", properties=" + properties + ", processInstance=" + processInstance);
        return processInstance.getId();
    }

    private ProcessInstance formServiceSubmitStartFormData(String processDefinitionId, Map<String, String> _properties) {
        try {
            return this.getFormService().submitStartFormData(processDefinitionId, _properties);
        } catch (Throwable e) {
            log.error("#119 formServiceSubmitStartFormData error, processDefinitionId=" + processDefinitionId + ", _properties=" + _properties + ", e=" + e, e);

            throw new RuntimeException("#120 formServiceSubmitStartFormData error, processDefinitionId=" + processDefinitionId + ", _properties=" + _properties + ", e=" + e, e);
        }
    }

    @Override
    public void saveFormData(String taskId, Map<String, String> properties) {
        Map<String, String> _stringMap = MapUtil.stringMap(properties);

        this.getFormService().saveFormData(taskId, _stringMap); // 好像只接受String类型
    }

    @Override
    public List<Map<String, Object>> listStartFormPropertyByTemplateId(String modelId) {
        XmlObject startEvent = this.findStartEvent(modelId);

        return this.listFormProperty(startEvent);
    }

    /**
     * @see #listHistoryFormPropertyByTaskId(String)
     * @see org.activiti.engine.FormService#submitTaskFormData(String,Map)提交参数并提交Task
     * @see org.activiti.engine.FormService#saveFormData(String, Map) 提交参数不提交Task
     */
    @Override
    public List<Map<String, Object>> listUserTaskForm(String taskId) {
        if (StringUtil.isNullOrEmpty(taskId)) {
            return null;
        }

        Task task = this.getTaskService().createTaskQuery() //
                .taskId(taskId) //
                .singleResult();

        String taskDefinitionKey = task.getTaskDefinitionKey();

        String processDefinitionId = this.findProcessDefinitionIdByTaskId(taskId);

        XmlObject _userTask = this.findUserTask(processDefinitionId, taskDefinitionKey);
        return this.listFormProperty(_userTask);
    }

    /**
     * @see #listUserTaskForm(String)
     */
    @Override
    public List<Map<String, Object>> listHistoryFormPropertyByTaskId(String executionId) {
        if (StringUtil.isNullOrEmpty(executionId)) {
            log.error("#151 listHistoryFormPropertyByTaskId, executionId=" + executionId);
            return null;
        }

        HistoricTaskInstance taskInstance = this.getHistoryService().createHistoricTaskInstanceQuery() //
                .taskId(executionId) // taskId
                .singleResult();

        // 是过程节点
        if (null != taskInstance) {
            return this.listHistoryUserTaskForm(executionId, taskInstance);
        }

        // 是开始节点
        return this.listHistoryStartForm(executionId);
    }

    private List<Map<String, Object>> listHistoryStartForm(String executionId) {
        String processDefinitionId = this.findProcessDefinitionIdByExecutionId(executionId);
        if (StringUtil.isNullOrEmpty(processDefinitionId)) {
            log.warn("#172 listHistoryStartForm return null, processDefinitionId=" + processDefinitionId);
            return null;
        }

        XmlObject startEvent = this.findStartEventByProcessDefinitionId(processDefinitionId);
        if (null == startEvent) {
            log.error("#142 startEvent=" + startEvent + ", processDefinitionId=" + processDefinitionId);
            return null;
        }

        List<Map<String, Object>> formProperties = this.listFormProperty(startEvent);

        if (null == formProperties || formProperties.isEmpty()) {
            log.info("#201 listHistoryStartForm, formProperties=" + formProperties + ", executionId=" + executionId);
            return formProperties;
        }

        // 值
        Map<String, Object> valueMap = this.findHistoryStartFormValueByExecutionId(executionId);

        // debug log
        log.debug("#208 listHistoryStartForm, formProperties={}, executionId={}, valueMap={}", formProperties, executionId, valueMap);

        for (Map<String, Object> map : formProperties) {
            String type = (String) map.get("type");
            String id = (String) map.get("id");

            String value = (String) valueMap.get(id);

            // 空值
            if (null == value || value.trim().isEmpty()) {
                map.put("_value", value);
                continue;
            }

            // 时间
            if (DateFormType.NAME.equalsIgnoreCase(type)) {
                String _value = Time.when(value) //
                        .plusHour(TimeFormType.TIME_ZONE) //
                        .toString(DateFormType.FORMAT_OUT);
                map.put("_value", _value);
                continue;
            }

            // 其他值
            map.put("_value", value);
        }

        return formProperties;
    }

    private XmlObject findStartEventByProcessDefinitionId(String processDefinitionId) {
        if (StringUtil.isEmpty(processDefinitionId)) {
            log.error("#207 findStartEventByProcessDefinitionId return null, processDefinitionId=" + processDefinitionId);
            return null;
        }

        InputStream processModel = this.getRepositoryService().getProcessModel(processDefinitionId);
        XmlObject xmlObject = Xml.parse(processModel);

        XmlObject definitions = xmlObject.getChildNodeArray()[0];
        XmlObject process = definitions.getChildNodeByName("process");
        XmlObject startEvent = process.getChildNodeByName("startEvent");
        return startEvent;
    }

    private Map<String, Object> findHistoryStartFormValueByExecutionId(String executionId) {
        List<HistoricDetail> historicDetails = this.getHistoryService().createHistoricDetailQuery() //
                .executionId(executionId) //
                .list();

        if (null == historicDetails || historicDetails.isEmpty()) {
            log.warn("#259 findHistoryStartFormValueByExecutionId, historicDetails=" + historicDetails + ", executionId=" + executionId);
            return new HashMap<String, Object>();
        }

        Map<String, Object> values = new HashMap<String, Object>();
        for (HistoricDetail historicDetail : historicDetails) {
            if (null == historicDetail || !StringUtil.isNullOrEmpty(historicDetail.getTaskId())) {
                log.info("#261 findHistoryStartFormValueByExecutionId, historicDetail=" + historicDetail);
                continue; // 为空的是启动节点，不为空的是过程节点
            }

            if (historicDetail instanceof HistoricFormPropertyEntityImpl) {
                HistoricFormPropertyEntityImpl formProperty = (HistoricFormPropertyEntityImpl) historicDetail;
                String propertyId = formProperty.getPropertyId();
                String propertyValue = formProperty.getPropertyValue();
                values.put(propertyId, propertyValue);
                continue;
            }

            if (historicDetail instanceof HistoricDetailVariableInstanceUpdateEntityImpl) {
                HistoricDetailVariableInstanceUpdateEntityImpl updateEntity = (HistoricDetailVariableInstanceUpdateEntityImpl) historicDetail;
                String propertyId = updateEntity.getName();
                String propertyValue = updateEntity.getTextValue();
                values.put(propertyId, propertyValue);
                continue;
            }

            log.error("#284 findHistoryStartFormValueByExecutionId, historicDetail=" + historicDetail);
        }

        return values;
    }

    private List<Map<String, Object>> listHistoryUserTaskForm(String taskId, HistoricTaskInstance taskInstance) {
        String taskDefinitionKey = taskInstance.getTaskDefinitionKey(); // 节点定义ID

        String processDefinitionId = this.findProcessDefinitionIdByTaskId(taskId);

        XmlObject _userTask = this.findUserTask(processDefinitionId, taskDefinitionKey);
        List<Map<String, Object>> formProperties = this.listFormProperty(_userTask);

        Map<String, Object> values = this.findHistoryFormValueByTaskId(taskId);

        log.info("#280 listHistoryUserTaskForm, taskId=" + taskId + ", taskInstance=" + taskInstance + ", taskDefinitionKey=" + taskDefinitionKey //
                + ", processDefinitionId=" + processDefinitionId + ", _userTask=" + _userTask + ", formProperties=" + formProperties + ", values=" + values);

        if (null != formProperties) {
            for (Map<String, Object> map : formProperties) {
                String type = (String) map.get("type");
                if (AllowDenyFormType.NAME.equals(type)) {
                    Object value = values.get("allow");
                    map.put("_value", value);
                    continue;
                }

                String id = (String) map.get("id");
                String value = (String) values.get(id);
                map.put("_value", value);
            }
        }

        return formProperties;
    }

    private Map<String, Object> findHistoryFormValueByTaskId(String taskId) {
        List<HistoricDetail> historicDetails = this.getHistoryService().createHistoricDetailQuery() //
                .taskId(taskId) // 有些情况下 这个属性是空的
                .list();

        if (null == historicDetails || historicDetails.isEmpty()) {
            historicDetails = this.getHistoryService().createHistoricDetailQuery() //
                    .executionId(taskId) //
                    .list();
        }

        // 数据比 executionId 查的多
        if (null == historicDetails || historicDetails.isEmpty()) {
            historicDetails = this.getHistoryService().createHistoricDetailQuery() //
                    .processInstanceId(taskId) //
                    .list();
        }

        Map<String, Object> values = new HashMap<String, Object>();
        for (HistoricDetail historicDetail : historicDetails) {
            HistoricFormPropertyEntityImpl formProperty = (HistoricFormPropertyEntityImpl) historicDetail;
            String propertyId = formProperty.getPropertyId();
            String propertyValue = formProperty.getPropertyValue();
            values.put(propertyId, propertyValue);
        }

        return values;
    }

    private List<Map<String, Object>> listFormProperty(XmlObject xmlObject) {
        if (null == xmlObject) {
            log.error("#293 listFormProperty, xmlObject=" + xmlObject);
            return null;
        }

        XmlObject extensionElements = xmlObject.getChildNodeByName("extensionElements");
        if (null == extensionElements) {
            log.warn("#299 listFormProperty, xmlObject=" + xmlObject);

            return null;
        }

        XmlObject[] formProperties = extensionElements.getChildNodeArrayByName("activiti:formProperty");

        List<Map<String, Object>> _formProperties = new ArrayList<Map<String, Object>>();
        for (XmlObject formProperty : formProperties) {
            Map<String, Object> _formProperty = this.formProperty(formProperty);
            _formProperties.add(_formProperty);
        }

        return _formProperties;
    }

    private XmlObject findStartEvent(String modelId) {
        XmlObject process = this.findProcessByModelId(modelId);
        if (null == process) {
            log.error("#314 findStartEvent, process = " + process + ", modelId=" + modelId);
            return null;
        }

        return process.getChildNodeByName("startEvent");
    }

    private XmlObject findUserTask(String processDefinitionId, String taskDefinitionKey) {
        String modelId = this.findModelIdByProcessDefinitionId(processDefinitionId);

        if (null == modelId) {
            log.error("#292 findUserTask, modelId is null, processDefinitionId=" + processDefinitionId + ", taskDefinitionKey=" + taskDefinitionKey);
            return null;
        }

        XmlObject process = this.findProcessByModelId(modelId);
        if (null == process) {
            log.error("#298 findUserTask, process is null, processDefinitionId=" + processDefinitionId + ", taskDefinitionKey=" + taskDefinitionKey + ", modelId=" + modelId);
            return null;
        }

        XmlObject[] userTasks = process.getChildNodeArrayByName("userTask");

        for (XmlObject userTask : userTasks) {
            String id = userTask.getAttributeValue("id");
            if (StringUtil.equalsIgnoreCase(taskDefinitionKey, id)) {
                return userTask;
            }
        }

        log.error("#311 findUserTask, return null, processDefinitionId=" + processDefinitionId + ", taskDefinitionKey=" + taskDefinitionKey + ", modelId=" + modelId);
        return null;
    }

    private XmlObject findProcessByModelId(String modelId) {
        if (StringUtil.isNullOrEmpty(modelId)) {
            log.error("#317 findProcessByModelId return null, modelId=" + modelId);
            return null;
        }

        byte[] modelEditorSource = this.getRepositoryService().getModelEditorSource(modelId);

        if (null == modelEditorSource || modelEditorSource.length < 1) {
            log.error("#356 findProcessByModelId, modelEditorSource=" + modelEditorSource + ", modelId=" + modelId);
            return null;
        }

        String bpmnXml = BpmnModelUtil.bpmnJsonToXml(modelEditorSource);

        XmlObject xmlObject = Xml.parseString(bpmnXml);
        return xmlObject.getChildNodeByName("process");
    }

    private Map<String, Object> formProperty(XmlObject formProperty) {
        String type = formProperty.getAttributeValue("type");
        String id = formProperty.getAttributeValue("id");
        String name = formProperty.getAttributeValue("name");
        String readable = formProperty.getAttributeValue("readable");
        String writable = formProperty.getAttributeValue("writable");
        String required = formProperty.getAttributeValue("required");
        String expression = formProperty.getAttributeValue("expression");

        Map<String, Object> prop = MapUtil.stringObjectMap("id", id, //
                "expression", expression, //
                "readable", readable, //
                "writable", writable, //
                "required", required, //
                "name", name, //
                "type", type);

        XmlObject[] enumValues = formProperty.getChildNodeArrayByName("activiti:value");
        if (null != enumValues && enumValues.length > 0) {
            List<Map<String, Object>> enums = new ArrayList<Map<String, Object>>();
            for (XmlObject enumValue : enumValues) {
                String _id = enumValue.getAttributeValue("id");
                String _name = enumValue.getAttributeValue("name");
                enums.add(MapUtil.stringObjectMap("name", _name, "id", _id));
            }

            prop.put("enums", enums);
        }

        return prop;
    }

    @Override
    public void deleteProcessInstances(List<String> processInstanceIds, String deleteReason) {
        log.info("#55 deleteProcessInstances, processInstanceIds=" + processInstanceIds + ", deleteReason=" + deleteReason);

        for (String processInstanceId : processInstanceIds) {
            this.deleteProcessInstance(processInstanceId, deleteReason);
        }
    }

    private void deleteProcessInstance(String processInstanceId, String deleteReason) {
        ProcessInstance processInstance = this.getRuntimeService().createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (null == processInstance) {
            log.error("#77 deleteProcessInstances, processInstanceId=" + processInstanceId + ", processInstance=" + processInstance);
            return;
        }

        // 已经挂起的不要再次挂起
        if (!processInstance.isSuspended()) {
            this.getRuntimeService().suspendProcessInstanceById(processInstanceId); // 挂起流程
        }

        this.getRuntimeService().deleteProcessInstance(processInstanceId, deleteReason); // 删除流程
    }

    @Override
    public void completeTask(String taskId, Map<String, Object> variables) {
        if (null == taskId) {
            log.error("#66 completeTask error, taskId=" + taskId + ", variables=" + variables);
            return;
        }

        log.info("#53 completeTask, taskId=" + taskId + ", variables=" + variables);
        this.getTaskService().complete(taskId, variables); // variables 可用于流程分支判断
    }

    /**
     * @see #submitStartFormData(String, Map)
     * @see #findModelEditorJson(String)
     */
    @Override
    public String startProcessInstanceByModelId(String modelId, Map<String, Object> variables) {
        String processDefinitionId = this.findProcessDefinitionIdByModelId(modelId);
        if (StringUtil.isNullOrEmpty(processDefinitionId)) {
            log.error("#376 startProcessInstanceByModelId, processDefinitionId=" + processDefinitionId + ", modelId=" + modelId);
            return null;
        }

        ProcessInstance processInstance = this.getRuntimeService().startProcessInstanceById(processDefinitionId, variables);

        log.info("#204 startProcessInstanceByModelId, processDefinitionId=" + processDefinitionId //
                + ", variables=" + variables + ", processInstance=" + processInstance);

        return processInstance.getId();
    }

    @Override
    public List<String> listHistoricActivityNameByVariable(String variableName, String variableValue) {
        List<HistoricActivityInstance> historicActivityInstances = this.listHistoryActivityByVariable(variableName, variableValue);
        if (null == historicActivityInstances || historicActivityInstances.isEmpty()) {
            log.error("#420 listHistoricActivityNameByVariable return null, variableName=" + variableName + ", variableValue=" + variableValue);
            return null;
        }

        return historicActivityInstances.stream().map(HistoricActivityInstance::getActivityName).collect(Collectors.toList());
    }

    /**
     * @see #startProcessInstanceByModelId(String, Map)
     */
    @Override
    public String findModelEditorJson(String modelId) {
        if (StringUtil.isNullOrEmpty(modelId)) {
            log.error("#465 findModelEditorJson return null, modelId=" + modelId);
            return null;
        }

        Model model = this.getRepositoryService().getModel(modelId);
        if (null == model) {
            log.error("#471 findModelEditorJson return null, model=" + model + ", modelId=" + modelId);
            return null;
        }

        String modelMetaInfo = model.getMetaInfo();

        byte[] modelEditorSourceBytes = this.getRepositoryService().getModelEditorSource(model.getId());
        String modelEditorSource = StringUtil.newUtf8String(modelEditorSourceBytes);

        JSONObject modelMetaInfoJson = JSON.parseObject(modelMetaInfo);
        JSONObject modelEditorSourceJson = JSON.parseObject(modelEditorSource);

        String metaDescription = null == modelMetaInfoJson ? null : modelMetaInfoJson.getString("description");
        String metaRevision = null == modelMetaInfoJson ? null : modelMetaInfoJson.getString("revision");

        Map<String, Object> modelEditorJsonMap = MapUtil.stringObjectMap( //
                ModelDataJsonConstants.MODEL_NAME, model.getName(), //
                ModelDataJsonConstants.MODEL_ID, modelId, //
                "description", metaDescription, //
                "revision", metaRevision, //
                "model", modelEditorSourceJson);

        return JSON.toJSONString(modelEditorJsonMap);
    }

    @Override
    public Boolean saveModel(String modelId, String name, String description, String jsonXml, String svgXml) {
        log.info("#235 saveModel, modelId=" + modelId + ", name=" + name + ", description=" + description);

        Model model = this.getRepositoryService().getModel(modelId);

        ObjectNode modelJson = (ObjectNode) ObjectMapperUtil.readTree(this.getObjectMapper(), model.getMetaInfo());

        modelJson.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelJson.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        model.setMetaInfo(modelJson.toString());
        model.setName(name);
        this.getRepositoryService().saveModel(model); // 更新

        byte[] modelEditorSource = StringUtil.getUtf8Bytes(jsonXml);
        this.getRepositoryService().addModelEditorSource(model.getId(), modelEditorSource);

        log.info("#90 bpmnXml=" + BpmnModelUtil.bpmnJsonToXml(modelEditorSource));

        byte[] pngBytes = PNGTranscoderUtil.pngTranscode(svgXml);
        this.getRepositoryService().addModelEditorSourceExtra(modelId, pngBytes);

        return true;
    }

    /**
     * @see #listModel()
     */
    @Override
    public Map<String, Object> findModelById(String modelId) {
        Model model = this.getRepositoryService().createModelQuery() //
                .modelId(modelId) //
                .singleResult();

        if (null == model) {
            log.warn("#499 findModelById, model=" + model + ", modelId=" + modelId);
            return null;
        }

        return this.modelToMap(model);
    }

    /**
     * ACT_RE_DEPLOYMENT 部署单元信息 ACT_RE_MODEL 模型信息 ACT_RE_PROCDEF 已部署的流程定义
     * 
     * @see #deployDeployment(String, String)
     * @see #findModelById(String)
     */
    @Override
    public List<Map<String, Object>> listModel() {
        List<Model> models = this.getRepositoryService().createModelQuery() //
                .list();

        List<Map<String, Object>> modelList = new ArrayList<Map<String, Object>>();
        for (Model model : models) {
            Map<String, Object> modelMap = this.modelToMap(model);
            modelList.add(modelMap);
        }

        return modelList;
    }

    private Map<String, Object> modelToMap(Model model) {
        String _description = null;

        String metaInfo = model.getMetaInfo();
        if (StringUtil.isNotNullOrEmpty(metaInfo)) {
            JSONObject jsonObject = JSON.parseObject(metaInfo);
            _description = jsonObject.getString("description");
        }

        return MapUtil.stringObjectMap("id", model.getId(), //
                "name", model.getName(), //
                "description", _description);
    }

    /**
     * @see #deployDeployment(String, String)
     */
    @Override
    public String createModel(String name, String key) {
        if (StringUtil.isNullOrEmpty(key)) {
            key = System.currentTimeMillis() + "";
        }

        Model model = this.getRepositoryService().newModel();
        ObjectNode modelNode = this.getObjectMapper().createObjectNode();

        modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, "");
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);

        model.setName(name);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());

        this.getRepositoryService().saveModel(model);
        this.addModelEditorSource(model.getId());

        return model.getId();
    }

    @Override
    public Boolean deployModel(String modelId) {
        log.info("#301 deployModel " + modelId);

        Model model = this.getRepositoryService().getModel(modelId);
        byte[] modelEditorSource = this.getRepositoryService().getModelEditorSource(model.getId());
        if (modelEditorSource == null) {
            log.info("#144 modelEditorSource=" + modelEditorSource + ", modelId=" + modelId);
            return false;
        }

        BpmnModel bpmnModel = BpmnModelUtil.bpmnJsonToModel(modelEditorSource);
        log.info("#146 bpmnXml=" + BpmnModelUtil.bpmnJsonToXml(modelEditorSource));

        // deploy
        Deployment deployment = this.getRepositoryService().createDeployment() //
                .name(model.getName()) //
                .addBpmnModel(model.getKey() + ".bpmn20.xml", bpmnModel) //
                .deploy();

        model.setDeploymentId(deployment.getId());
        this.getRepositoryService().saveModel(model);

        return true;
    }

    /**
     * @see #deleteModel(String)
     */
    @Override
    public Boolean deleteDeploymentByModelId(String modelId) {
        Model model = this.getRepositoryService().getModel(modelId);
        if (null == model) {
            log.error("#570 deleteDeploymentByModelId, no model for id " + modelId);
            return false;
        }

        String deploymentId = model.getDeploymentId();
        if (StringUtil.isNullOrEmpty(deploymentId)) {
            log.warn("#576 deleteDeploymentByModelId, no deployment for model,deploymentId=" + deploymentId + ", modelId=" + modelId);
            return true;
        }

        // 参数不加true: 为普通删除, 如果当前规则下有正在执行的流程, 则抛异常
        // 参数加true: 为级联删除, 会删除和当前规则相关的所有信息, 包括历史
        this.getRepositoryService().deleteDeployment(deploymentId /* ,true */ );
        return true;
    }

    /**
     * @see #deleteDeploymentByModelId(String)
     */
    @Override
    public Boolean deleteModel(String modelId) {
        Model model = this.getRepositoryService().getModel(modelId);
        if (null == model) {
            log.error("#341 deleteModel, model=" + model + ", modelId=" + modelId);
            return false;
        }

        this.deleteProcessInstanceAndHistoryByProcessDefinitionKey(model.getKey());

        this.getRepositoryService().deleteModel(modelId);
        return true;
    }

    private void deleteProcessInstanceAndHistoryByProcessDefinitionKey(String processDefinitionKey) {
        String processInstanceId = this.findProcessInstanceIdByProcessDefinitionKey(processDefinitionKey);
        if (null == processInstanceId) {
            log.error("#432 processInstanceId=" + processInstanceId + ", processDefinitionKey=" + processDefinitionKey);
            return;
        }

        this.getRuntimeService().deleteProcessInstance(processInstanceId, "#424 deleteReason");
        this.getHistoryService().deleteHistoricProcessInstance(processInstanceId);
    }

    private String findProcessInstanceIdByProcessDefinitionKey(String processDefinitionKey) {
        if (StringUtil.isNullOrEmpty(processDefinitionKey)) {
            log.error("#347 processDefinitionKey=" + processDefinitionKey);
            return null;
        }

        ProcessInstance processInstance = this.getRuntimeService().createProcessInstanceQuery() //
                .processDefinitionKey(processDefinitionKey) //
                .singleResult();

        if (null == processInstance) {
            log.error("#538 processDefinitionKey=" + processDefinitionKey);
            return null;
        }

        return processInstance.getId();
    }

    /**
     * 创建模型时完善ModelEditorSource
     */
    private void addModelEditorSource(String modelId) {
        log.info("#230 createObjectNode" + modelId);

        ObjectNode editorNode = this.getObjectMapper().createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");

        ObjectNode stencilSetNode = this.getObjectMapper().createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.set("stencilset", stencilSetNode);

        byte[] utf8Bytes = StringUtil.getUtf8Bytes(editorNode.toString());
        this.getRepositoryService().addModelEditorSource(modelId, utf8Bytes);

        log.info("#244 after createObjectNode, " + modelId);
    }

    private List<HistoricActivityInstance> listHistoryActivityByVariable(String variableName, String variableValue) {
        String processInstanceId = this.findTaskProcessInstanceIdByVariable(variableName, variableValue);

        if (null == processInstanceId) {
            return null;
        }

        // 开始 部门经理审核 总经理审核 等目前位置已经执行的步骤
        List<HistoricActivityInstance> historicActivityInstances = this.getHistoryService().createHistoricActivityInstanceQuery() //
                .processInstanceId(processInstanceId) //
                .orderByHistoricActivityInstanceStartTime() //
                .asc() // 排序
                .list();

        return historicActivityInstances;
    }

    private String findTaskProcessInstanceIdByVariable(String variableName, String variableValue) {
        List<Task> tasks = this.getTaskService().createTaskQuery() //
                .processVariableValueEquals(variableName, variableValue) //
                .listPage(0, 1);

        if (null == tasks || tasks.isEmpty() || tasks.size() > 1) {
            log.warn("#58, findTaskProcessInstanceIdByVariable return null, tasks=" + tasks + ", variableName=" + variableName + ", variableValue=" + variableValue);
            return null;
        }

        Task task = tasks.get(0);
        return task.getProcessInstanceId();
    }

    /**
     * 如果一个工单有多个流程，而后续流程未写成功，就会一直查询出第一个
     */
    @Override
    public String findExecutionIdByVariable(String variableName, String variableValue) {
        List<Task> tasks = this.getTaskService().createTaskQuery() //
                .processVariableValueEquals(variableName, variableValue) //
                .orderByTaskCreateTime().desc() //
                .listPage(0, 1);

        if (null == tasks || tasks.isEmpty()) {
            log.info("#785 findExecutionIdByVariable, variableName=" + variableName + ", variableValue=" + variableValue);
            return null;
        }

        Task task = tasks.get(0);
        return task.getExecutionId();
    }

    @Override
    public String findTaskIdByVariable(String variableName, String variableValue) {
        List<String> taskIds = this.listTaskIdByVariable(variableName, variableValue);
        if (null == taskIds || taskIds.isEmpty()) {
            log.warn("#773 findTaskIdByVariable, taskIds=" + taskIds + ", variableName=" + variableName + ", variableValue=" + variableValue);
            return null;
        }

        return taskIds.get(0);
    }

    @Override
    public List<String> listTaskIdByVariable(String variableName, String variableValue) {
        List<Task> list = this.getTaskService().createTaskQuery().processVariableValueEquals(variableName, variableValue).list();
        if (null == list || list.isEmpty()) {
            log.warn("#785 listTaskIdByVariable, list=" + list + ", variableName=" + variableName + ", variableValue=" + variableValue);
            return Collections.EMPTY_LIST;
        }

        Object ids = list.stream().map(Task::getId).collect(Collectors.toList());
        return (List<String>) ids;
    }

    @Override
    public List<String> listTaskProcessInstanceIdByVariable(String variableName, String variableValue) {
        List<Task> list = this.getTaskService().createTaskQuery().processVariableValueEquals(variableName, variableValue).list();

        Object ids = list.stream().map(Task::getProcessInstanceId).collect(Collectors.toList());
        return (List<String>) ids;
    }

    private String findModelIdByProcessDefinitionId(String processDefinitionId) {
        ProcessDefinition processDefinition = this.getRepositoryService().getProcessDefinition(processDefinitionId);

        if (null == processDefinition) {
            log.error("#653 processDefinition=" + processDefinition + ", processDefinitionId=" + processDefinitionId);
            return null;
        }

        Model model = this.getRepositoryService().createModelQuery() //
                .deploymentId(processDefinition.getDeploymentId()) //
                .singleResult();

        if (null == model) {
            log.error("#657 model=" + model + ", processDefinition=" + processDefinition);
            return null;
        }

        return model.getId();
    }

    private String findProcessDefinitionIdByExecutionId(String executionId) {
        if (StringUtil.isNullOrEmpty(executionId)) {
            log.error("#792 findProcessDefinitionIdByExecutionId return null, executionId=" + executionId);
            return null;
        }

        List<HistoricTaskInstance> historicTaskInstances = this.getHistoryService().createHistoricTaskInstanceQuery() //
                .executionId(executionId) //
                .listPage(0, 1);

        if (null == historicTaskInstances || historicTaskInstances.isEmpty()) {
            historicTaskInstances = this.getHistoryService().createHistoricTaskInstanceQuery() //
                    .processInstanceId(executionId) //
                    .listPage(0, 1);
        }

        if (null == historicTaskInstances || historicTaskInstances.isEmpty()) {
            log.warn("#795 findProcessDefinitionIdByExecutionId return null, historicTaskInstances=" + historicTaskInstances + ", executionId=" + executionId);
            return null;
        }

        HistoricTaskInstance historicTaskInstance = historicTaskInstances.get(0);
        return historicTaskInstance.getProcessDefinitionId();
    }

    private String findProcessDefinitionIdByTaskId(String taskId) {
        return this.getHistoryService().createHistoricTaskInstanceQuery() //
                .taskId(taskId) //
                .singleResult() //
                .getProcessDefinitionId();
    }

    private String findProcessDefinitionIdByModelId(String modelId) {
        Model model = this.getRepositoryService().createModelQuery() //
                .modelId(modelId) //
                .singleResult();

        if (null == model) {
            log.error("#711 model=" + model + ", modelId=" + modelId);
            return null;
        }

        String deploymentId = model.getDeploymentId();
        if (StringUtil.isNullOrEmpty(deploymentId)) {
            log.error("#717 model=" + model + ", modelId=" + modelId + ", deploymentId=" + deploymentId);
            return null;
        }

        ProcessDefinition processDefinition = this.getRepositoryService().createProcessDefinitionQuery() //
                .deploymentId(deploymentId) //
                .singleResult();

        if (null == processDefinition) {
            log.error("#725 processDefinition=" + processDefinition + ", modelId=" + modelId + ", deploymentId=" + deploymentId);
            return null;
        }

        return processDefinition.getId();
    }

    @Override
    public String findLastAssigneeByVariable(String variableName, String variableValue) {
        HistoricActivityInstance activity = this.findLastHistoryActivityByVariable(variableName, variableValue);

        // 处理人更新为最新的工作流节点
        return null == activity ? null : activity.getAssignee();
    }

    @Override
    public String findLastActivityIdByVariable(String variableName, String variableValue) {
        HistoricActivityInstance activity = this.findLastHistoryActivityByVariable(variableName, variableValue);
        return null == activity ? null : activity.getActivityId();
    }

    private HistoricActivityInstance findLastHistoryActivityByVariable(String variableName, String variableValue) {
        String processInstanceId = this.findTaskProcessInstanceIdByVariable(variableName, variableValue);

        if (null == processInstanceId) {
            return null;
        }

        // 目前位置已经执行的步骤，如 开始 部门经理审核 总经理审核
        List<HistoricActivityInstance> activityInstances = this.getHistoryService().createHistoricActivityInstanceQuery() //
                .processInstanceId(processInstanceId) //
                .orderByHistoricActivityInstanceStartTime() //
                .desc() // 排序
                .listPage(0, 1);

        HistoricActivityInstance lastActivityInstance = ListUtil.last(activityInstances);
        if (null == lastActivityInstance) {
            log.error("#255 lastActivityInstance=" + lastActivityInstance + ", activityInstances=" + activityInstances);
            return null;
        }

        return lastActivityInstance;
    }

    @Override
    public List<Integer> listLastCandidateByVariable(String variableName, String variableValue) {
        HistoricActivityInstance lastActivity = this.findLastHistoryActivityByVariable(variableName, variableValue);
        if (null == lastActivity //
                || StringUtil.isNullOrEmpty(lastActivity.getTaskId()) //
                || "endEvent".equals(lastActivity.getActivityType())) {
            log.info("#745 lastActivity=" + lastActivity + ", variableName=" + variableName + ", variableValue=" + variableValue + ", this=" + this);
            return null;
        }

        TaskEntity taskEntity = this.getTaskEntityManager().findById(lastActivity.getTaskId());
        if (null == taskEntity) {
            log.error("#819 listLastCandidateByVariable, taskEntity=" + taskEntity + ", lastActivity=" + lastActivity + ", this=" + this);
            return null;
        }

        List<IdentityLink> identityLinks = this.getTaskService().getIdentityLinksForTask(lastActivity.getTaskId());
        List<Integer> candidateIds = new ArrayList<Integer>();
        for (IdentityLink identityLink : identityLinks) {
            if (StringUtil.equalsIgnoreCase(IdentityLinkType.CANDIDATE, identityLink.getType())) {
                String userId = identityLink.getUserId();
                Integer candidateId = NumberUtil.toInteger(userId);
                candidateIds.add(candidateId);
            }
        }
        return candidateIds;
    }

    @Override
    public String findModelIdByprocessInstanceId(String processInstanceId) {
        ProcessInstance processInstance = this.getRuntimeService().createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (null == processInstance) {
            log.error("#77 deleteProcessInstances, processInstanceId=" + processInstanceId + ", processInstance=" + processInstance);
            return null;
        }

        String processDefinitionId = processInstance.getProcessDefinitionId();
        return this.findModelIdByProcessDefinitionId(processDefinitionId);
    }

    /**
     * @see #listHistoryFormPropertyByTaskId(String)
     */
    @Override
    public Map<String, Object> findHistoryFormPropertyByTaskIdAndFieldName(String taskId, String fieldName) {
        List<Map<String, Object>> list = this.listHistoryFormPropertyByTaskId(taskId);
        if (null == list || list.isEmpty()) {
            log.error("#896 findHistoryFormPropertyByTaskIdAndFieldName, list is null, list=" + list);
            return null;
        }

        for (Map<String, Object> each : list) {
            String key = (String) each.get("id");
            if (StringUtil.equals(fieldName, key)) {
                return each;
            }
        }

        log.error("#907 findHistoryFormPropertyByTaskIdAndFieldName return null, taskId=" + taskId + ", fieldName=" + fieldName + ", list=" + list);
        return null;
    }

    private Boolean insertHistoryDetail_task(String taskId, String fieldName, Object fieldValue) {
        Connection connection = JdbcUtil.getConnection(this.getDataSource());
        String sql = "INSERT INTO `ACT_HI_DETAIL`(`ID_`, `TASK_ID_`, `NAME_`, `TEXT_`, `TYPE_`, `TIME_`) VALUES(?, ?, ?, ?, ?, ?)";
        Object[] parameters = new Object[] { "task-" + taskId + "-" + fieldName, taskId, fieldName, fieldValue, "FormProperty", new Date() };
        Integer executeUpdate = JdbcUtil.executeUpdate(connection, sql, parameters);
        return 0 < executeUpdate;
    }

    private Boolean insertHistoryDetail_start(String executionId, String fieldName, Object fieldValue) {
        Connection connection = JdbcUtil.getConnection(this.getDataSource());
        String sql = "INSERT INTO `ACT_HI_DETAIL`(`ID_`, `EXECUTION_ID_`, `NAME_`, `TEXT_`, `TYPE_`, `TIME_`) VALUES(?, ?, ?, ?, ?, ?)";
        Object[] parameters = new Object[] { "executionId-" + executionId + "-" + fieldName, executionId, fieldName, fieldValue, "FormProperty", new Date() };
        Integer executeUpdate = JdbcUtil.executeUpdate(connection, sql, parameters);
        return 0 < executeUpdate;
    }

    @Override
    public Object parseFieldValue(String taskId, String fieldName, Object fieldValue) {
        Map<String, Object> map = this.findHistoryFormPropertyByTaskIdAndFieldName(taskId, fieldName);
        if (null == map || map.isEmpty()) { // 表单定义
            log.error("#994 parseFieldValue, map=" + map + ", taskId=" + taskId + ", fieldName=" + fieldName + ", fieldValue=" + fieldValue);
            return fieldValue;
        }

        String type = (String) map.get("type"); // 特殊类型处理
        if (StringUtil.contains(type, "_range") || StringUtil.contains(type, "multi_ticket_user")) {
            return JSON.toJSONString(fieldValue);
        }

        return fieldValue; // 其他原样返回
    }

    @Override
    public Boolean updateHistoryDetail(String taskId, String fieldName, Object fieldValue) {
        if (StringUtil.isNullOrEmpty(taskId) || StringUtil.isNullOrEmpty(fieldName)) {
            log.error("#1008 updateHistoryDetail return false, taskId=" + taskId + ", fieldName=" + fieldName + ", fieldValue=" + fieldValue);
            return false;
        }

        HistoricTaskInstance taskInstance = this.getHistoryService().createHistoricTaskInstanceQuery() //
                .taskId(taskId) // taskId
                .singleResult();

        // 是过程节点
        if (null != taskInstance) {
            return this.updateHistoryDetail_task(taskId, fieldName, fieldValue);
        }

        // 是开始节点
        return this.updateHistoryDetail_start(taskId, fieldName, fieldValue);
    }

    @Override
    public Object findHistoryDetail(String taskId, String fieldName) {
        if (StringUtil.isNullOrEmpty(taskId) || StringUtil.isNullOrEmpty(fieldName)) {
            log.error("#1002 findHistoryDetail return null, taskId=" + taskId + ", fieldName=" + fieldName);
            return null;
        }

        HistoricTaskInstance taskInstance = this.getHistoryService().createHistoricTaskInstanceQuery() //
                .taskId(taskId) // taskId
                .singleResult();

        // 是过程节点
        if (null != taskInstance) {
            return this.findHistoryDetail_task(taskId, fieldName);
        }

        // 是开始节点
        return this.findHistoryDetail_start(taskId, fieldName);
    }

    /**
     * @see #updateHistoryDetail_start(String, String, Object)
     */
    private Object findHistoryDetail_start(String executionId, String fieldName) {
        String sql = "SELECT `TEXT_` FROM `ACT_HI_DETAIL` WHERE `EXECUTION_ID_`=? AND `NAME_`=?";
        Object[] parameters = new Object[] { executionId, fieldName };
        List<Map<String, Object>> result = JdbcUtil.executeQuery(this.getDataSource(), sql, parameters);

        if (null == result || result.isEmpty() || null == result.get(0) || result.get(0).isEmpty()) {
            return null;
        }

        return result.get(0).get("TEXT_");
    }

    /**
     * @see #updateHistoryDetail_task(String, String, Object)
     */
    private Object findHistoryDetail_task(String taskId, String fieldName) {
        String sql = "SELECT `TEXT_` FROM `ACT_HI_DETAIL` WHERE `TASK_ID_`=? AND `NAME_`=?";
        Object[] parameters = new Object[] { taskId, fieldName };
        List<Map<String, Object>> result = JdbcUtil.executeQuery(this.getDataSource(), sql, parameters);

        if (null == result || result.isEmpty() || null == result.get(0) || result.get(0).isEmpty()) {
            return null;
        }

        return result.get(0).get("TEXT_");
    }

    /**
     * @see #findHistoryDetail_start(String, String)
     */
    private Boolean updateHistoryDetail_start(String executionId, String fieldName, Object fieldValue) {
        Connection connection = JdbcUtil.getConnection(this.getDataSource());
        String sql = "UPDATE `ACT_HI_DETAIL` SET `TEXT_`=?, `TIME_`=? WHERE `EXECUTION_ID_`=? AND `NAME_`=?";

        // 数组 以 JSON 形式存 其他的 暂不动
        fieldValue = null != fieldValue && fieldValue.getClass().isArray() ? Fastjson.toJSONString(fieldValue) : fieldValue;

        Object[] parameters = new Object[] { fieldValue, new Date(), executionId, fieldName };
        Integer executeUpdate = JdbcUtil.executeUpdate(connection, sql, parameters);

        if (0 < executeUpdate) {
            return true;
        }

        return this.insertHistoryDetail_start(executionId, fieldName, fieldValue);
    }

    /**
     * @see #findHistoryDetail_task(String, String)
     */
    private Boolean updateHistoryDetail_task(String taskId, String fieldName, Object fieldValue) {
        Connection connection = JdbcUtil.getConnection(this.getDataSource());
        String sql = "UPDATE `ACT_HI_DETAIL` SET `TEXT_`=?, `TIME_`=? WHERE `TASK_ID_`=? AND `NAME_`=?";
        Object[] parameters = new Object[] { fieldValue, new Date(), taskId, fieldName };
        Integer executeUpdate = JdbcUtil.executeUpdate(connection, sql, parameters);

        if (0 < executeUpdate) {
            return true;
        }

        return this.insertHistoryDetail_task(taskId, fieldName, fieldValue);
    }
}